﻿using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using SoundEngine3;
using SoundEngine3.DspPlugins.Meta;

namespace ZquenceStudio3Host.DspPlugins.Synthesizers.Nodesizer.Engine.Modules.Container
{
    [ModuleDisplay("Group")]
    public class GroupModule : BaseModule, IModuleContainer
    {
        private List<BaseModule> mSubModules = new List<BaseModule>();

        public static IList<ParameterProperties> GetVisibleProperties(BaseModule module)
        {
            IList<ParameterProperties> properties = module.Parameters;
            if (module is GroupModule)
            {
                properties = (module as GroupModule).GetVisibleParameters();
            }

            return properties;
        }

        public ReadOnlyCollection<BaseModule> SubModules
        {
            get;
        }

        public GroupModule(ModuleManager manager)
            : base(manager)
        {
            SubModules = new ReadOnlyCollection<BaseModule>(mSubModules);
        }

        protected GroupModule(SoundEngineContext soundEngine)
            : base(soundEngine)
        {
            SubModules = new ReadOnlyCollection<BaseModule>(mSubModules);
        }

        public override void Process()
        {

        }

        public void AddSubModule(BaseModule module)
        {
            IncludeSubModule(module);

            Manager.AddModule(module);
        }

        public void IncludeSubModule(BaseModule module)
        {
            if (mSubModules.Contains(module))
            {
                throw new Exception("Module already exist.");
            }
            mSubModules.Add(module);
            
            module.ParentModule = this;
        }

        public void RemoveSubModule(BaseModule module)
        {
            Manager.RemoveModule(module);

            ExcludeSubModule(module);
        }

        public void ExcludeSubModule(BaseModule module)
        {
            if (!mSubModules.Contains(module))
            {
                throw new Exception("Module do not exist. Can not remove.");
            }
            mSubModules.Remove(module);
            
            module.ParentModule = null;
        }

        public List<ModuleInput> GetVisibleInputs()
        {
            return _GetNonConnectedInputs(this);
        }

        public void GetVisibleInputs(List<ModuleInput> inputs)
        {
            _GetNonConnectedInputs(this, inputs);
        }

        public List<ModuleOutput> GetVisibleOutputs()
        {
            return _GetNonConnectedOutputs(this);
        }

        public void GetVisibleOutputs(List<ModuleOutput> outputs)
        {
            _GetNonConnectedOutputs(this, outputs);
        }

        public List<ParameterProperties> GetVisibleParameters()
        {
            return _GetVisibleParameters(this);
        }

        private List<ModuleInput> _GetNonConnectedInputs(GroupModule relatedFrom)
        {
            List<ModuleInput> tmpinputs = new List<ModuleInput>();
            _GetNonConnectedInputs(relatedFrom, tmpinputs);
            return tmpinputs;
        }

        private void _GetNonConnectedInputs(GroupModule relatedFrom, List<ModuleInput> inputs)
        {
            foreach (var subModule in mSubModules)
            {
                // Get non connected inputs from sub module.
                if (subModule is GroupModule)
                {
                    (subModule as GroupModule)._GetNonConnectedInputs(relatedFrom, inputs);
                }

                // Get non connected inputs.
                foreach (var input in GetInputsFromModule(subModule, relatedFrom))
                {
                    if (input.InternallyVisible)
                    {
                        continue;
                    }

                    // TODO: Behövs denhär checken?
                    if (!inputs.Contains(input))
                    {
                        // If not connected, then show it.
                        if (!input.IsConnected)
                        {
                            inputs.Add(input);
                        }
                        else if(relatedFrom.ParentModule == null)
                        {
                            // Is connected to a module inside this group.
                        }
                        //else if (InputExistInAny(input, relatedFrom.SubModules))
                        else if (!InputConnectedToAnyInner(input, relatedFrom.SubModules))
                        {
                            // But if it is connected outside from current group module's sub module, then show it as well.
                            inputs.Add(input);
                        }
                    }
                }
            }
        }

        private List<ModuleOutput> _GetNonConnectedOutputs(GroupModule relatedFrom)
        {
            List<ModuleOutput> tmpoutputs = new List<ModuleOutput>();
            _GetNonConnectedOutputs(relatedFrom, tmpoutputs);
            return tmpoutputs;
        }

        private void _GetNonConnectedOutputs(GroupModule relatedFrom, List<ModuleOutput> outputs)
        {
            foreach (var subModule in mSubModules)
            {
                // Get non connected inputs from sub module.
                if (subModule is GroupModule)
                {
                    (subModule as GroupModule)._GetNonConnectedOutputs(relatedFrom, outputs);
                }

                // Get non connected inputs.
                var subOutputs = GetOutputsFromModule(subModule, relatedFrom);
                foreach (var output in subOutputs)
                {
                    if (output.InternallyVisible)
                    {
                        continue;
                    }

                    // TODO: Behövs denhär checken?
                    if (!outputs.Contains(output))
                    {
                        if (!output.IsConnected)
                        {
                            outputs.Add(output);
                        }
                        else if (relatedFrom.ParentModule == null)
                        {

                        }
                        //else if (OutputConnectedToAny(output, relatedFrom.SubModules))
                        else if (!OutputConnectedToAnyInner(output, relatedFrom.SubModules))
                        {
                            // But if it is connected outside from current group module's sub module, then show it as well.
                            outputs.Add(output);
                        }
                    }
                }
            }
        }
        
        private List<ParameterProperties> _GetVisibleParameters(GroupModule relatedFrom)
        {
            List<ParameterProperties> outputs = new List<ParameterProperties>();

            foreach (var subModule in mSubModules)
            {
                // Get non connected inputs from sub module.
                if (subModule is GroupModule)
                {
                    outputs.AddRange((subModule as GroupModule)._GetVisibleParameters(relatedFrom));
                }

                // Get non connected inputs.
                var subOutputs = GetParametersFromModule(subModule, relatedFrom);
                foreach (var parameter in subOutputs)
                {
                    if ((parameter as ModuleParameter).InternallyVisible)
                    {
                        continue;
                    }

                    // TODO: Behövs denhär checken?
                    if (!outputs.Contains(parameter))
                    {
                        if (parameter is ModuleParameter)
                        {
                            outputs.Add(parameter);
                        }
                        else if (relatedFrom.Parameters.Contains(parameter))
                        {
                            outputs.Add(parameter);
                        }
                    }
                }
            }

            return outputs;
        }

        IEnumerable<ModuleInput> GetInputsFromModule(BaseModule module, GroupModule relatedFrom)
        {
            if (module is GroupModule)
            {
                return (module as GroupModule)._GetNonConnectedInputs(relatedFrom);
            }
            else
            {
                return module.Inputs;
            }
        }

        IEnumerable<ModuleOutput> GetOutputsFromModule(BaseModule module, GroupModule relatedFrom)
        {
            if (module is GroupModule)
            {
                return (module as GroupModule)._GetNonConnectedOutputs(relatedFrom);
            }
            else
            {
                return module.Outputs;
            }
        }

        IEnumerable<ParameterProperties> GetParametersFromModule(BaseModule module, GroupModule relatedFrom)
        {
            if (module is GroupModule)
            {
                return (module as GroupModule)._GetVisibleParameters(relatedFrom);
            }
            else
            {
                return module.Parameters;
            }
        }

        private bool InputConnectedToAnyInner(ModuleInput input, IEnumerable<BaseModule> modules)
        {
            foreach (var module in modules)
            {
                if (module is GroupModule)
                {
                    bool existHere = InputConnectedToAnyInner(input, (module as GroupModule).SubModules);
                    if (existHere)
                    {
                        return true;
                    }
                }

                foreach (var output in module.Outputs)
                {
                    if (input.FromOutput == output)
                    {
                        return true;
                    }
                }
            }

            return false;
        }

        private bool OutputConnectedToAnyInner(ModuleOutput output, IEnumerable<BaseModule> modules)
        {
            foreach (var module in modules)
            {
                if (module is GroupModule)
                {
                    bool existHere = OutputConnectedToAnyInner(output, (module as GroupModule).SubModules);
                    if (existHere)
                    {
                        return true;
                    }
                }

                foreach (var input in module.Inputs)
                {
                    if (input.FromOutput == output)
                    {
                        return true;
                    }
                }
            }

            return false;
        }
    }
}
